package com.ycproxy;

import jdk.internal.reflect.Reflection;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.security.KeyFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class YcJdkProxy {
    private static final String ln = "\r\n";

    /**
     * a cache of proxy classes
     */


    public static Object newProxyInstance(YcClassLoader ycClassLoader,
                                          Class<?>[] interfaces,
                                          YcInvocationHandler h) {
        try {
            // 生成代理类的源码
            String src = generateSrc(interfaces);
            // .java输出到磁盘
            String filePath = YcJdkProxy.class.getResource("").getPath();
            filePath = URLDecoder.decode(filePath, "utf-8");
            File f = new File(filePath + interfaces[0].getSimpleName() + "_Proxy.java");
            FileWriter fw = new FileWriter(f);
            fw.write(src);
            fw.flush();
            fw.close();

            // 编译源码
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null);
            Iterable iterable = manage.getJavaFileObjects(f);

            Boolean call = compiler.getTask(null, manage, null, null, null, iterable).call();
            if (!call) {
                throw new RuntimeException("编译失败");
            }
            manage.close();
            // 加载编译后的class文件
//            YcClassLoader ycClassLoader = new YcClassLoader();
//            Object aClass = Class.forName("com.ycproxy." + interfaces[0].getSimpleName() + "_Proxy");
            Class<?> proxy = ycClassLoader.findClass("OrderBiz_Proxy");
            Constructor c = proxy.getConstructor(YcInvocationHandler.class);
            f.delete();
            return c.newInstance(h);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String generateSrc(Class<?>[] interfaces) {
        StringBuilder sb = new StringBuilder();
        String fullName = interfaces[0].getName(); // 获取完整类名，包含包名
        String simpleName = interfaces[0].getSimpleName(); // 获取简单类名，不包含包名
        String packageName = fullName.substring(0, fullName.length() - simpleName.length() - 1);
        sb.append("package " + "ycproxy" + ";" + ln);
        sb.append("import com.ycproxy.YcInvocationHandler;" + ln);
        sb.append("import java.lang.reflect.*;" + ln);
        sb.append("public class " + simpleName + "_Proxy implements " + fullName + " {" + ln);
        sb.append("public YcInvocationHandler h;" + ln);
        sb.append("public " + simpleName + "_Proxy(YcInvocationHandler h) {" + ln);
        sb.append("this.h = h;" + ln);
        sb.append("}" + ln);
        for (Method method : interfaces[0].getMethods()) {
            Class<?>[] params = method.getParameterTypes();
            StringBuffer paramsNames = new StringBuffer();
            StringBuilder paramValues = new StringBuilder();
            StringBuilder paramClasses = new StringBuilder();
            for (int i = 0; i < params.length; i++) {
                Class<?> clazz = params[i];
                String type = clazz.getName();
//                String paramName = toLowerFirstCase(clazz.getSimpleName());
                paramsNames.append(type + " " + "var" + i + ",");
                paramValues.append("var" + i + ",");
                paramClasses.append(clazz.getName() + ".class,");
//                if (i > 0 && i <= params.length - 1) {
//                    paramsNames.append(", ");
//                    paramValues.append(", ");
//                    paramClasses.append(", ");
//                }
            }
            if (paramClasses.length() > 0 && paramClasses.charAt(paramClasses.length() - 1) == ',') {
                paramClasses.deleteCharAt(paramClasses.length() - 1);
            }
            if (paramsNames.length() > 0 && paramsNames.charAt(paramsNames.length() - 1) == ',') {
                paramsNames.deleteCharAt(paramsNames.length() - 1);
            }
            if (paramValues.length() > 0 && paramValues.charAt(paramValues.length() - 1) == ',') {
                paramValues.deleteCharAt(paramValues.length() - 1);
            }
            sb.append("public " + method.getReturnType().getName() + " " + method.getName() + "(" + paramsNames.toString() + ") {" + ln);
            sb.append("try {" + ln);
            sb.append("Method m = " + fullName + ".class.getMethod(\"" + method.getName() + "\", new Class[]{" + paramClasses.toString() + "});" + ln);
            sb.append((hasReturnValue(method.getReturnType()) ? "return" : " ") +
                    getCaseCode(" this.h.invoke(this, m, new Object[]{" + paramValues.toString() + "})", method.getReturnType()) + ";" + ln);
            sb.append("} catch (Error _ex) { " + ln);
            sb.append("}" + ln);
            sb.append(" catch (Throwable e) {" + ln);
            sb.append("throw new UndeclaredThrowableException(e);" + ln);
            sb.append("}" + ln);
            sb.append(getReturnEmptyCode(method.getReturnType()) + ln);
            sb.append("}" + ln);

        }
        sb.append("}" + ln);
        return sb.toString();
    }


    private static Map<Class, Class> mappings = new HashMap<>();

    static {
        mappings.put(int.class, Integer.class);
    }

    private static String getReturnEmptyCode(Class<?> returnType) {
        if (mappings.containsKey(returnType)) {
            return "return 0;";
        } else if (returnType == void.class) {
            return "return;";
        } else {
            return "return null;";
        }
    }

    private static String getCaseCode(String code, Class<?> returnType) {
        if (mappings.containsKey(returnType)) {
            return "((" + mappings.get(returnType).getName() + ")" + code + ")." + returnType.getSimpleName() + "Value()";
        }
        return code;
    }

    private static boolean hasReturnValue(Class<?> returnType) {
        return returnType != void.class;
    }

    private static String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
//        chars[0] += 32;  // 转换成小写
        return String.valueOf(chars);
    }


}
